%%%-------------------------------------------------------------------
%%% @author mirahs(2892727493@qq.com)
%%% @doc
%%%     热更管理器
%%% @end
%%%-------------------------------------------------------------------
-module(sys_code).

-export([
    init/0

    ,beam_file/0
    ,beam_hash/0
    ,beam_hash/1

    ,up/0
    ,up/1
    ,up/2
]).

-include("common.hrl").


%%%===================================================================
%%% API functions
%%%===================================================================

init() ->
    ?ECHO("正在启动[~w]", [?MODULE]),
    ets:new(?MODULE, [named_table, public, set]),
    [ets:insert(?MODULE, X) || X <- beam_hash()],
    ?ECHO("完成启动[~w]", [?MODULE]).

%% 获取ebin目录下的beam文件列表
beam_file() ->
    case file:list_dir("./ebin") of
        {ok, FList} ->
            {ok, do_beam_file(FList, [])};
        {error, Why} ->
            {error, Why}
    end.

%% 获取所有beam文件的hash值
beam_hash() ->
    case beam_file() of
        {ok, F} ->
            do_beam_hash(F, []);
        {error, Why} ->
            {error, Why}
    end.

%% 获取指定beam文件的hash值
beam_hash(M) ->
    do_beam_hash([M], []).

%% 重新检查并加载所有的模块
up() ->
    N = beam_hash(),
    O = ets:tab2list(?MODULE),
    do_up(N, O, [], fun code:soft_purge/1).

%% 重新检查并加载所有的模块，并强制清除运行中的旧代码
up(force) ->
    N = beam_hash(),
    O = ets:tab2list(?MODULE),
    do_up(N, O, [], fun code:purge/1);

%% 重新检查并加载指定的模块
up(ModList) ->
    N = do_beam_hash(ModList, []),
    O = ets:tab2list(?MODULE),
    do_up(N, O, [], fun code:soft_purge/1).

%% 重新检查并加载指定的模块
up(ModList, force) ->
    N = do_beam_hash(ModList, []),
    O = ets:tab2list(?MODULE),
    do_up(N, O, [], fun code:purge/1).


%%%===================================================================
%%% Internal functions
%%%===================================================================

%% 返回所有的beam文件
do_beam_file([], List) -> List;
do_beam_file([F | T], List) ->
    % F = "sys_code.beam"
    NL =
        case filename:extension(F) of
            ".beam" ->
                M = filename:basename(filename:rootname(F)),
                [M | List];
            _ -> List
        end,
    do_beam_file(T, NL).

%% 返回beam hash
do_beam_hash([], List) -> List;
do_beam_hash([N | T], List) ->
    NL =
        case beam_lib:md5("./ebin/" ++ N) of
            {ok, {M, Md5}} ->
                [{M, util_encrypt:md5(Md5)} | List];
            {error, Why} ->
                ?ERR("无法获取文件[~p]的hash值:~p", [N, Why]),
                List
        end,
    do_beam_hash(T, NL).

%% 执行更新
do_up([], _O, Rtn, _Fun) -> Rtn;
do_up([{Mod, NewHash} | N], O, Rtn, Fun) ->
    NewRtn =
        case lists:keyfind(Mod, 1, O) of
            false ->
                [load_beam(Mod, NewHash, Fun) | Rtn];
            {_, OldHash} ->
                case OldHash of
                    NewHash -> Rtn;
                    _ -> [load_beam(Mod, NewHash, Fun) | Rtn]
                end
        end,
    do_up(N, O, NewRtn, Fun).

%% 加载beam文件(热更新)
load_beam(Mod, Hash, PurgeFun) ->
    PurgeFun(Mod),
    case code:load_file(Mod) of
        {module, _} ->
            ets:insert(?MODULE, {Mod, Hash}),
            {Mod, ok};
        {error, Why} ->
            {Mod, {error, Why}}
    end.
